﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.ViewModels;
using MugenMvvmToolkitTest.TestInfrastructure;
using MugenMvvmToolkitTest.TestModels;
using Should;

namespace MugenMvvmToolkitTest.ViewModel
{
    [TestClass]
    public class EditableViewModelTest : ValidatableViewModelTest
    {
        #region Nested types

        public class ApplyChangesClass
        {
            public IList<IEntityStateEntry> EntityStateEntries { get; set; }

            public object Entity { get; set; }
        }

        public class TestEditableViewModel : EditableViewModel<object>
        {

        }

        public class EventEditableViewModel : EditableViewModel<object>
        {
            #region Properties

            public IEntityStateProvider CustomStateProvider { get; set; }

            public Func<object, object> SaveStateDelegate { get; set; }

            public Func<ApplyChangesClass> ApplyChangesInternalDelegate { get; set; }

            public Func<object, IList<IEntityStateEntry>> GetChangesDelegate { get; set; }

            public Func<object> CancelChangesInternalDelegate { get; set; }

            public Action<IList<IEntityStateEntry>> OnChangesAppliedDelegate { get; set; }

            public bool IsEntityInitializedInvoked { get; set; }

            public bool IsChangesCanceledInvoked { get; set; }

            #endregion

            #region Overrides of EditableViewModel<object>

            /// <summary>
            ///     Gets an instance of <see cref="IEntityStateProvider" />.
            /// </summary>
            /// <returns>
            ///     An instance of <see cref="IEntityStateProvider" />.
            /// </returns>
            protected override IEntityStateProvider GetEntityStateProvider()
            {
                if (CustomStateProvider != null)
                    return CustomStateProvider;
                return base.GetEntityStateProvider();
            }

            /// <summary>
            ///     Saves a state of entity.
            /// </summary>
            /// <param name="entity">The specified entity.</param>
            /// <returns>An instance of object.</returns>
            protected override object SaveState(object entity)
            {
                if (SaveStateDelegate != null)
                    return SaveStateDelegate(entity);
                return base.SaveState(entity);
            }

            /// <summary>
            ///     Occurs after an entity instance is initialized.
            /// </summary>
            protected override void OnEntityInitialized()
            {
                IsEntityInitializedInvoked = true;
            }

            /// <summary>
            ///     Applies the changes of entity.
            /// </summary>
            /// <returns>A series of instances of <see cref="IEntityStateEntry" />.</returns>
            protected override IList<IEntityStateEntry> ApplyChangesInternal(out object entity)
            {
                if (ApplyChangesInternalDelegate != null)
                {
                    var applyChangesInternalDelegate = ApplyChangesInternalDelegate();
                    entity = applyChangesInternalDelegate.Entity;
                    return applyChangesInternalDelegate.EntityStateEntries;
                }
                return base.ApplyChangesInternal(out entity);
            }

            /// <summary>
            ///     Gets the changes.
            /// </summary>
            /// <param name="entity">The saved entity.</param>
            /// <returns>A series of instances of <see cref="IEntityStateEntry" />.</returns>
            protected override IList<IEntityStateEntry> GetChanges(object entity)
            {
                if (GetChangesDelegate != null)
                    return GetChangesDelegate(entity);
                return base.GetChanges(entity);
            }

            /// <summary>
            ///     Occurs after applying changes.
            /// </summary>
            /// <param name="entityStateEntries">The entity state entries.</param>
            protected override void OnChangesApplied(IList<IEntityStateEntry> entityStateEntries)
            {
                if (OnChangesAppliedDelegate != null)
                    OnChangesAppliedDelegate(entityStateEntries);
            }

            /// <summary>
            ///     Cancels the changes of entity.
            /// </summary>
            /// <returns>An instance of object.</returns>
            protected override object CancelChangesInternal()
            {
                if (CancelChangesInternalDelegate != null)
                    return CancelChangesInternalDelegate();
                return base.CancelChangesInternal();
            }

            /// <summary>
            ///     Occurs after canceling changes.
            /// </summary>
            protected override void OnChangesCanceled()
            {
                IsChangesCanceledInvoked = true;
            }

            #endregion
        }

        #endregion

        #region Properties

        public TestStateProvider StateProvider { get; private set; }

        #endregion

        #region Test methods

        [TestMethod]
        public void EntityShouldBeNullBeforeInitializedIfFactoryNull()
        {
            GlobalSettings.DefaultEntityFactory = null;
            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.Entity.ShouldBeNull();
        }

        [TestMethod]
        public void EntityShouldBeNotNullBeforeInitializedIfFactoryNotNull()
        {
            GlobalSettings.DefaultEntityFactory = Activator.CreateInstance;
            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.Entity.ShouldNotBeNull();
        }

        [TestMethod]
        public void IsEntityInitializedShouldBeFalseBeforeInitialized()
        {
            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.IsEntityInitialized.ShouldBeFalse();
        }

        [TestMethod]
        public void ApplyChangesShouldThrowExceptionIfVmNotInitialized()
        {
            var viewModel = GetViewModel<TestEditableViewModel>();
            ShouldThrow<InvalidOperationException>(() => viewModel.ApplyChanges());
        }

        [TestMethod]
        public void CancelChangesShouldThrowExceptionIfVmNotInitialized()
        {
            var testEditableViewModel = GetViewModel<TestEditableViewModel>();
            ShouldThrow<InvalidOperationException>(() => testEditableViewModel.CancelChanges());
        }

        [TestMethod]
        public void DoubleInitializeEntityShouldNotThrowException()
        {
            StateProvider.SaveDelegate = o => o;
            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.InitializeEntity(new object(), true);
            viewModel.InitializeEntity(new object(), true);
        }

        [TestMethod]
        public void InitializeEntityShouldSaveStateUsingStateProvider()
        {
            bool isInvoked = false;
            StateProvider.SaveDelegate = o =>
                                         {
                                             isInvoked = true;
                                             return o;
                                         };
            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.InitializeEntity(new object(), true);
            isInvoked.ShouldBeTrue();
        }

        [TestMethod]
        public void ApplyChangesShouldApllyStateUsingStateProvider()
        {
            bool isInvoked = false;
            StateProvider.SaveDelegate = o => o;
            StateProvider.ApplyDelegate = o =>
                                          {
                                              isInvoked = true;
                                              return o;
                                          };
            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.InitializeEntity(new object(), true);
            viewModel.ApplyChanges();
            isInvoked.ShouldBeTrue();
        }

        [TestMethod]
        public void CancelChangesShouldCancelStateUsingStateProvider()
        {
            bool isInvoked = false;
            StateProvider.SaveDelegate = o => o;
            StateProvider.CancelDelegate = o =>
            {
                isInvoked = true;
                return o;
            };
            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.InitializeEntity(new object(), true);
            viewModel.CancelChanges();
            isInvoked.ShouldBeTrue();
        }

        [TestMethod]
        public void InitializeEntityShouldGetEntityFromStateProvider()
        {
            var fromProvider = new object();
            StateProvider.SaveDelegate = o => fromProvider;

            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.InitializeEntity(new object(), true);
            viewModel.Entity.ShouldEqual(fromProvider);
        }

        [TestMethod]
        public void CancelChangesShouldGetEntityFromStateProvider()
        {
            var fromProvider = new object();
            StateProvider.SaveDelegate = o => o;
            StateProvider.CancelDelegate = o => fromProvider;

            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.InitializeEntity(new object(), true);
            viewModel.CancelChanges().ShouldEqual(fromProvider);
        }

        [TestMethod]
        public void ApplyChangesShouldGetEntityFromStateProvider()
        {
            var entity = new object();
            var fromProvider = new object();
            StateProvider.SaveDelegate = o => o;
            StateProvider.ApplyDelegate = o => fromProvider;

            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.InitializeEntity(entity, true);
            viewModel.Entity.ShouldEqual(entity);
            viewModel.ApplyChanges();
            viewModel.Entity.ShouldEqual(fromProvider);
        }

        [TestMethod]
        public void ApplyChangesShouldReturnEntityStateEntryWithEntityAdded()
        {
            var entity = new object();
            StateProvider.SaveDelegate = o => o;
            StateProvider.ApplyDelegate = o => o;

            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.InitializeEntity(entity, true);
            var entityStateEntries = viewModel.ApplyChanges();
            entityStateEntries.Single().Entity.ShouldEqual(entity);
            entityStateEntries.Single().State.ShouldEqual(EntityState.Added);
        }

        [TestMethod]
        public void ApplyChangesShouldReturnEntityStateEntryWithEntityModified()
        {
            var entity = new object();
            StateProvider.SaveDelegate = o => o;
            StateProvider.ApplyDelegate = o => o;

            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.InitializeEntity(entity, false);
            var entityStateEntries = viewModel.ApplyChanges();
            entityStateEntries.Single().Entity.ShouldEqual(entity);
            entityStateEntries.Single().State.ShouldEqual(EntityState.Modified);
        }

        [TestMethod]
        public void HasChangesShouldGetValueFromStateProviderIfItIsSupportChangeDetection()
        {
            bool stateValue = false;
            var entity = new object();
            StateProvider.SaveDelegate = o => o;
            StateProvider.HasChangesDelegate = o => stateValue;
            StateProvider.IsSupportChangeDetection = true;

            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.InitializeEntity(entity, false);
            viewModel.HasChanges.ShouldBeFalse();
            stateValue = true;
            viewModel.HasChanges.ShouldBeTrue();
        }

        [TestMethod]
        public void HasChangesShouldNotGetValueFromStateProviderIfItIsNotSupportChangeDetection()
        {
            bool isInvoked = false;
            var entity = new object();
            StateProvider.SaveDelegate = o => o;
            StateProvider.HasChangesDelegate = o =>
                                               {
                                                   isInvoked = true;
                                                   return false;
                                               };
            StateProvider.IsSupportChangeDetection = false;

            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.InitializeEntity(entity, false);
            viewModel.HasChanges.ShouldBeFalse();
            isInvoked.ShouldBeFalse();
        }

        [TestMethod]
        public void HasChangesShouldSetValue()
        {
            var entity = new object();
            StateProvider.SaveDelegate = o => o;
            StateProvider.IsSupportChangeDetection = false;

            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.InitializeEntity(entity, false);
            viewModel.HasChanges.ShouldBeFalse();
            viewModel.HasChanges = true;
            viewModel.HasChanges.ShouldBeTrue();
        }

        [TestMethod]
        public void EntityInitializedGenericShouldRaiseEvent()
        {
            bool isInvoked = false;
            var entity = new object();
            StateProvider.SaveDelegate = o => o;

            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.EntityInitialized += (model, o) =>
                                           {
                                               isInvoked = true;
                                               o.ShouldEqual(entity);
                                               model.ShouldEqual(viewModel);
                                           };
            viewModel.InitializeEntity(entity, false);
            isInvoked.ShouldBeTrue();
        }

        [TestMethod]
        public void EntityInitializedNonGenericShouldRaiseEvent()
        {
            bool isInvoked = false;
            var entity = new object();
            StateProvider.SaveDelegate = o => o;

            IEditableViewModel viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.EntityInitialized += (model, o) =>
            {
                isInvoked = true;
                o.ShouldEqual(entity);
                model.ShouldEqual(viewModel);
            };
            viewModel.InitializeEntity(entity, false);
            isInvoked.ShouldBeTrue();
        }

        [TestMethod]
        public void ChangesAppliedGenericShouldRaiseEvent()
        {
            bool isInvoked = false;
            var entity = new object();
            StateProvider.SaveDelegate = o => o;
            StateProvider.ApplyDelegate = o => o;

            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.ChangesApplied += (model, o) =>
                                        {
                                            isInvoked = true;
                                            o.Single().Entity.ShouldEqual(entity);
                                            model.ShouldEqual(viewModel);
                                        };
            viewModel.InitializeEntity(entity, false);
            viewModel.ApplyChanges();
            isInvoked.ShouldBeTrue();
        }

        [TestMethod]
        public void ChangesAppliedNonGenericShouldRaiseEvent()
        {
            bool isInvoked = false;
            var entity = new object();
            StateProvider.SaveDelegate = o => o;
            StateProvider.ApplyDelegate = o => o;

            IEditableViewModel viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.ChangesApplied += (model, o) =>
            {
                isInvoked = true;
                o.Single().Entity.ShouldEqual(entity);
                model.ShouldEqual(viewModel);
            };
            viewModel.InitializeEntity(entity, false);
            viewModel.ApplyChanges();
            isInvoked.ShouldBeTrue();
        }

        [TestMethod]
        public void ChangesCanceledGenericShouldRaiseEvent()
        {
            bool isInvoked = false;
            var entity = new object();
            StateProvider.SaveDelegate = o => o;
            StateProvider.CancelDelegate = o => o;

            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.ChangesCanceled += (model, o) =>
                                         {
                                             isInvoked = true;
                                             o.ShouldEqual(entity);
                                             model.ShouldEqual(viewModel);
                                         };
            viewModel.InitializeEntity(entity, false);
            viewModel.CancelChanges();
            isInvoked.ShouldBeTrue();
        }

        [TestMethod]
        public void ChangesCanceledNonGenericShouldRaiseEvent()
        {
            bool isInvoked = false;
            var entity = new object();
            StateProvider.SaveDelegate = o => o;
            StateProvider.CancelDelegate = o => o;

            IEditableViewModel viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.ChangesCanceled += (model, o) =>
            {
                isInvoked = true;
                o.ShouldEqual(entity);
                model.ShouldEqual(viewModel);
            };
            viewModel.InitializeEntity(entity, false);
            viewModel.CancelChanges();
            isInvoked.ShouldBeTrue();
        }

        [TestMethod]
        public void IsNewRecordShouldBeTrueIfIsNewEntity()
        {
            StateProvider.SaveDelegate = o => o;
            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.InitializeEntity(new object(), true);
            viewModel.IsNewRecord.ShouldBeTrue();
        }

        [TestMethod]
        public void IsNewRecordShouldBeFalseIfIsNotNewEntity()
        {
            StateProvider.SaveDelegate = o => o;
            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.InitializeEntity(new object(), false);
            viewModel.IsNewRecord.ShouldBeFalse();
        }

        [TestMethod]
        public void VmShouldAddValidatorsForEntity()
        {
            var entity = new object();
            StateProvider.SaveDelegate = o => o;
            var viewModel = GetViewModel<TestEditableViewModel>();
            viewModel.Validators.ShouldBeEmpty();

            ValidatorProvider.Register<SpyValidator>();
            viewModel.InitializeEntity(entity, true);
            var validator = (SpyValidator)viewModel.Validators.Single();
            validator.Context.InstanceToValidate.ShouldEqual(entity);
        }

        [TestMethod]
        public void GetEntityStateProviderShouldReturnMainVmProvider()
        {
            bool isInvoked = false;
            var provider = new TestStateProvider();
            provider.SaveDelegate = o =>
            {
                isInvoked = true;
                return o;
            };
            var entity = new object();
            var viewModel = GetViewModel<EventEditableViewModel>();
            viewModel.CustomStateProvider = provider;
            viewModel.InitializeEntity(entity, true);
            isInvoked.ShouldBeTrue();
            viewModel.StateProvider.ShouldEqual(provider);
        }

        [TestMethod]
        public void ChangesCanceledInternalShouldRaiseEvent()
        {
            bool isInvoked = false;
            var entity = new object();
            StateProvider.SaveDelegate = o => o;
            StateProvider.CancelDelegate = o => o;

            var viewModel = GetViewModel<EventEditableViewModel>();
            viewModel.CancelChangesInternalDelegate = () => isInvoked = true;

            viewModel.InitializeEntity(entity, false);
            viewModel.CancelChanges();
            isInvoked.ShouldBeTrue();
            viewModel.IsChangesCanceledInvoked.ShouldBeTrue();
        }

        [TestMethod]
        public void GetChangesShouldBeUsedAsMainChanges()
        {
            bool isInvoked = false;
            bool isInvokedChangesApplied = false;
            StateProvider.SaveDelegate = o => o;
            StateProvider.ApplyDelegate = o => o;
            var entries = new IEntityStateEntry[0];
            var entity = new object();
            StateProvider.SaveDelegate = o => o;
            StateProvider.CancelDelegate = o => o;

            var viewModel = GetViewModel<EventEditableViewModel>();
            viewModel.GetChangesDelegate = (o) =>
            {
                isInvoked = true;
                o.ShouldEqual(entity);
                return entries;
            };
            viewModel.OnChangesAppliedDelegate = list =>
            {
                list.ShouldEqual(entries);
                isInvokedChangesApplied = true;
            };

            viewModel.InitializeEntity(entity, false);
            viewModel.ApplyChanges().ShouldEqual(entries);
            isInvoked.ShouldBeTrue();
            isInvokedChangesApplied.ShouldBeTrue();
        }

        [TestMethod]
        public void ApplyChangesInternalShouldBeUsedAsMainEntityAndChangesMethod()
        {
            bool isInvoked = false;
            StateProvider.SaveDelegate = o => o;
            StateProvider.ApplyDelegate = o => o;
            var entries = new IEntityStateEntry[0];
            var entity = new object();
            var viewModel = GetViewModel<EventEditableViewModel>();
            viewModel.ApplyChangesInternalDelegate = () =>
            {
                isInvoked = true;
                return new ApplyChangesClass
                {
                    Entity = entity,
                    EntityStateEntries = entries
                };
            };

            viewModel.InitializeEntity(new object(), true);
            viewModel.ApplyChanges().ShouldEqual(entries);
            viewModel.Entity.ShouldEqual(entity);
            isInvoked.ShouldBeTrue();
        }

        #endregion

        #region Overrides of TestBase

        protected override ValidatableViewModel GetValidatableViewModel()
        {
            StateProvider.SaveDelegate = o => o;
            var editableViewModel = GetViewModel<TestEditableViewModel>();
            editableViewModel.InitializeEntity(new object(), true);
            editableViewModel.RemoveInstance(editableViewModel.Entity);
            return editableViewModel;
        }

        protected override object GetFunc(Type type, string s, IIocParameter[] arg3)
        {
            if (type == typeof(IEntityStateProvider))
                return StateProvider;
            return base.GetFunc(type, s, arg3);
        }

        protected override void OnInit()
        {
            StateProvider = new TestStateProvider();
            base.OnInit();
        }

        #endregion
    }
}
